package oop.ex2.main;

import java.util.ArrayList;
import java.util.regex.*;


/**
 * This class represents a variable object.
 * @author Yoav Goldstein
 *
 */
public class Variable extends Patterns {

    //the variable's values
    private String type;
    private String name;
    private String value;
    private boolean isFinal;

    /**
     * Constructs a new variable object. the constructor checks weather the declaration of the new variable is
     * legal. the constructor can recognise the various types of declarations, and operate by
     * the declaration type.
     * 
     * @param line
     *            - The line to be checked
     * @param varArray - the array of global variables allready declared
     * @throws compileException
     * @throws VariableException
     * @throws MethodException
     */
    public Variable(String line, ArrayList<Variable> varArray, ArrayList<Method> methodArray, Method onMethod)
            throws compileException, VariableException, MethodException {
        //for the patterns
        super(line);
        if (line.matches(VARIABLE_DECLARATION)) {
            checkFullPattern(line, methodArray, varArray,onMethod);
        }
        // in case of short pattern declaration
        else if (line.matches(VARIABLE_SHORT_DECLARATION)) {
            checkShortPattern(line);
        }
        // throw exception in case of wrong declaration
        else {
            throw new compileException("The line: <" + line + "> - mismatch to variable declaration");
        }

    }
    /**
     * Matches the line in a full pattern phrase
     * @param line - the line to check
     * @param methodArray - all the declared methods
     * @param varArray - all the declared variables
     * @param onMethod - The method which the variable was created in
     * @throws VariableException
     * @throws MethodException
     * @throws compileException
     */
    private void checkFullPattern(String line, ArrayList<Method> methodArray, ArrayList<Variable> varArray, Method onMethod) throws VariableException, MethodException, compileException {
        Pattern fullPattern = Pattern.compile(VARIABLE_DECLARATION);
        Matcher fullMatcher = fullPattern.matcher(line);
        fullMatcher.matches();
        
        // Assign the values of the Variable according to the groups in the
        // regex

        // check for type validity
        this.type = fullMatcher.group(2);
        if (!type.matches(VARIABLE_TYPE)) {
            throw new VariableException("variable type <" + type + "> mismatch to variable type");
        }
        // check for name validity
        this.name = fullMatcher.group(3);
        if (!name.matches(Patterns.VARIABLE_NAME)) {
            throw new VariableException("variable name <" + name + "> mismatch to name pattern");
        }
        // check weather the variable is final
        this.value = fullMatcher.group(4);
        if (fullMatcher.group(1) != null) {
            this.isFinal = true;

        }
        // if is a check in a method, see if the arguments match
        if (onMethod != null) {
            for (Method mainmethod : methodArray) {
                if (mainmethod.getName().equals(onMethod.getName())) {
                    for (Variable argument : mainmethod.getVariables()) {
                        if (argument.getName().equals(value)) {
                            System.out.println("match argument assignment");
                            return;
                        }
                    }
                }
            }
        }
        // if the value is one of the other variables, gets the value to the
        // variable
        if (collectValue(fullMatcher.group(4).trim(), varArray) != null) {

            this.value = collectValue(fullMatcher.group(4).trim(), varArray);

        }

        // see if the value is a call for method
        else if ((fullMatcher.group(4) + ";".trim()).matches(METHOD_CALL)) {
            String newValue = fullMatcher.group(4).replaceAll("\\s+", "");
            
            Method method = new Method(newValue + ";", methodArray, varArray, this,null);
            // see if the method is in the method array
            if (method.contains(methodArray)) {
                // match the return type
                if (this.type.equals(method.getReturnType())) {
                    System.out.println("call <" + line + ">, match return type: " + method.getReturnType());
                }
                else {
                    throw new MethodException("method return type mismatch");
                }
            }
        }
        // check if the value matches the type variable declared
        else {
            VariableType type = VariableType.valueOf(fullMatcher.group(2).trim().toUpperCase());
            type.checkRegex(type.getRegex(), this.value.trim(), fullMatcher.group(2).trim());
            System.out.println("match! type: " + type.toString() + ", name: " + name + ", value: " + value
                    + ", final: " + isFinal);
        }        
    }

    /**
     * checks the variable for assignment without initialization
     * @param line - the line to be checked
     * @throws VariableException
     */
    private void checkShortPattern(String line) throws VariableException {
        // a pattern and matcher for short declatarion
        Pattern shortPattern = Pattern.compile(VARIABLE_SHORT_DECLARATION);
        Matcher shortMatcher = shortPattern.matcher(line);
        shortMatcher.matches();
        // add the values, throws exception for pattern validity
        this.type = shortMatcher.group(2);
        if (!type.matches(VARIABLE_TYPE)) {
            throw new VariableException("variable type <" + type + "> mismatch to variable type");
        }
        this.name = shortMatcher.group(3);
        if (!name.matches(Patterns.VARIABLE_NAME)) {
            throw new VariableException("variable name <" + name + "> mismatch to name pattern");
        }
        this.value = null;
        if (shortMatcher.group(1) != null) {
            throw new VariableException("must instantiate final variable");
        }

        System.out.println("match! type: " + type.toString() + ", name: " + name + ", value: " + value);
    }

    /**
     * collect a variable's value from a variable array
     * @param string - the variable's name to collect the value from
     * @param varArray - the array of variables to scan
     * @return the value of the specified variable, null otherwise
     */
    private String collectValue(String string, ArrayList<Variable> varArray) {
        //scans the variable array
        for (Variable var : varArray) {
            if (string.trim().equals(var.getName().trim())) {
                //return the variable's value
                return var.getValue();
            }
        }
        //returns null for a case of mismatch
        return null;
    }

    /**
     * checks weather this variable exists in an ArrayList of Variables, compared by name
     * 
     * @param variables
     *            - the array of variables
     * @return true if the variable exists in the array, false otherwise
     */
    public boolean exists(ArrayList<Variable> variables) {
        boolean exists = false;
        //scans the variable array
        for (Variable variable : variables) {
            if (this.getName().trim().equals(variable.getName().trim())) {
                // true if the name matches
                exists = true;
                break;
            }
        }
        //return the result
        return exists;
    }

    /**
     * gets the variable's name
     * @return the name of the variable
     */
    public String getName() {
        return this.name;
    }

    /**
     * gets the variable's value
     * @return the value of the specified variable
     */
    public String getValue() {
        return this.value;
    }

    /**
     * gets the type of the variable
     * @return the type of the variable
     */
    public String getType() {
        return this.type;
    }

    /**
     * returns if the variable is final or not
     * @return true if the variable is final, false otherwise
     */
    public boolean isFinal() {
        return isFinal;
    }

    /**
     * sets the variables value
     * @param value - the value to set the variable to
     */
    public void setVariable(String value) {
        this.value = value;
    }
}
